#include "fileTransfer.h"

/*
 * @brief 服务端处理客户端下载请求
 *
 * @param sockfd 连接客户端的socket文件描述符
 * @param mysql 连接到的数据库
 * @param file_path 文件在数据库中的路径，避免不同路径的同名文件
 * @param user_id 传入filePath_t 结构体用于获取用户唯一标识符 user_id
 *
 * @return 0-成功
 */
int downloadHandle(int sockfd, MYSQL *mysql,
                   const char *file_path, const filePath_t user_id)
{
    tlv_t server_tlv;
    fileCheck_t file_check;
    int flag;      // 暂存通信标识
    int mmap_flag; // 是否使用共享内存

    // 接收文件名
    recv(sockfd, &server_tlv, TLV_HEAD_SIZE, MSG_WAITALL);
    char *file_name = (char *)malloc(server_tlv.length + 1);
    recv(sockfd, file_name, server_tlv.length, MSG_WAITALL);
    file_name[server_tlv.length] = '\0';
    printf("filename: %s\n", file_name);

    // 查询数据库是否有这个文件
    flag = checkFileExist(mysql, user_id, file_name, file_path);
    server_tlv.type = TYPE_FLAG;
    server_tlv.length = sizeof(flag);
    send(sockfd, &server_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL); // 发送头
    send(sockfd, &flag, server_tlv.length, MSG_NOSIGNAL);   // 实际发送长度=头+校验标识

    if (flag == FILE_NOT_EXIST)
    {
        printf("没这文件，传个蛋\n");
        return -1;
    }

    // 如果网盘文件存在，则接收client标识决定是否需要续传
    recv(sockfd, &server_tlv, TLV_HEAD_SIZE, MSG_WAITALL);
    recv(sockfd, &flag, server_tlv.length, MSG_WAITALL);

    if (flag == FILE_EXIST) // client维护的续传文件存在，我管你这那，开始续传
    {
        // 接收 file_check 续传文件数据
        recv(sockfd, &server_tlv, TLV_HEAD_SIZE, MSG_WAITALL);
        recv(sockfd, &file_check, server_tlv.length, MSG_WAITALL);

        // 接收文件offset，从offset开始续传
        int offset = file_check.file_offset;

        // 打开文件并设置开始传输的位置
        int fd = open(file_check.sha256, O_RDONLY);
        lseek(fd, offset, SEEK_SET);

        // 向client发送数据
        mmap_flag = 0;
        serverSendData(sockfd, fd, &file_check, mmap_flag);

        printf("%s 传输完成\n", file_name);
        close(fd);
    }
    else if (server_tlv.length == FILE_NOT_EXIST)
    {
        // 查询数据库，准备 file_check 元数据，发送给 client 维护
        int file_size;
        char file_hash[SHA256_LENGTH];
        getFileSize(mysql, file_name, file_path, &file_size);
        getFileHash(mysql, file_name, file_path, file_hash);

        file_check.file_offset = 0;
        file_check.file_size = file_size;
        strncpy(file_check.sha256, file_hash, SHA256_LENGTH);
        strncpy(file_check.file_name, file_name, strlen(file_name) + 1);

        // 发送 file_check 元数据
        server_tlv.type = TYPE_FILE_CHECK;
        server_tlv.length = sizeof(fileCheck_t);
        send(sockfd, &server_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL); // 发送头
        send(sockfd, &file_check, server_tlv.length, MSG_NOSIGNAL);

        // 打开 client 请求下载的文件开始传输
        int fd = open(file_name, O_RDONLY);
        mmap_flag = 1;
        serverSendData(sockfd, fd, &file_check, mmap_flag);

        close(fd);
    }
    else
    {
        printf("要么羊\n");
        return -1;
    }

    free(file_name);
    return 0;
}

/*
 * @brief 服务端给客户端发送文件
 *
 * @param sockfd 连接客户端的socket文件描述符
 * @param fd 需要传输的文件的描述符
 * @param file_check 续传文件结构体指针
 * @param mmap_flag 续传和小文件不用 mmap
 *
 * @return 0-成功
 */
int serverSendData(int sockfd, int fd, fileCheck_t *file_check, int mmap_flag)
{
    tlv_t send_tlv;
    int file_size = file_check->file_size - file_check->file_offset;
    // 传输文件
    if (file_size > BIG_FILE_SIZE && mmap_flag) // 大文件且是全新下载，用 mmap 实现零拷贝
    {
        // ftruncate(fd, filesize);

        // 建立映射区
        char *p = (char *)mmap(NULL, file_size,
                               PROT_READ, MAP_SHARED, fd, 0); // 权限和open一致

        off_t offset = 0; // 偏移量

        send_tlv.type = TYPE_FILE_CHUNK;
        while (1)
        {
            if (offset >= file_size) // 跳出循环条件
            {
                break;
            }
            if (file_size - offset > CHUNK_SIZE) // 未传输数据量
            {
                send_tlv.length = CHUNK_SIZE;
            }
            else // 最后一次传输
            {
                send_tlv.length = file_size - offset;
            }

            send(sockfd, &send_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL);
            send(sockfd, p + offset, send_tlv.length, MSG_NOSIGNAL); // 从内核态到内核态
            offset += send_tlv.length;
        }

        send_tlv.type = TYPE_ABORT;
        send_tlv.length = 0;
        send(sockfd, &send_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL); // 发送一个空车厢表示传输完毕

        // 释放映射区
        munmap(p, file_size);
    }
    else // 小文件或续传
    {
        char *data = (char *)malloc(CHUNK_SIZE);
        while (1)
        {
            bzero(data, CHUNK_SIZE);                       // 清空车厢
            ssize_t ret_read = read(fd, data, sizeof(data)); // 将文件内容读取到火车厢

            send_tlv.length = ret_read;
            if (ret_read == 0) // 最后一次read已经没有数据了，ret=0，因此最后发送了一个空车厢
            {
                send_tlv.type = TYPE_ABORT;
                send(sockfd, &send_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL);
                break;
            }
            send_tlv.type = TYPE_FILE_CHUNK; // 设置火车头
            send(sockfd, &send_tlv, TLV_HEAD_SIZE, MSG_NOSIGNAL);
            send(sockfd, &data, send_tlv.length, MSG_NOSIGNAL);
            // send(sockfd, &send_tlv, sizeof(send_tlv.length) + send_tlv.length, MSG_NOSIGNAL);
        }
        free(data);
    }
    return 0;
}